<?php
/**
 *
 */
namespace Yz\modules\Modules;

use Yz_Array,
    Yz_Hash;

class Model_CreateList
    extends \Yz\modules\Generic\Model_List
{

    protected $modulesList;     // array hash { modName: reflection }
    protected $oldModulesList;  // hash { modName: module-item }
    protected $oldCtlList;  // hash { modName+ctlName: ctl-item }
    protected $moduleAttrs;
    protected $titleCols = array();
    public $ctlList;


    /** re-check installed modules and update the database
     *
     *  @return chainable
     */
    public function updateModulesList()
    {
        try {
            $this->oldModulesList = $this->fetchList();
        } catch (\Yz_Exception $ex) {
            //
            debug($ex->getTraceAsString());
        }
        $this->buildModulesList();
        $this->moduleAttrs = $this->buildModulesAttrs();
        $this->ctlList = $this->buildCtlList();

        $this->createTmpTables();
        $this->storeModuleAttrs($this->moduleAttrs);
        $this->storeCtlAttrs($this->ctlList);

        return $this;
    }



    /** create tables for temporary lists
     *
     */
    protected function createTmpTables()
    {
        $tableList = array(
            'yz_modules_tmp',
            'yz_modules_actions_tmp',
        );
        $stmt = \Yz_SQL_Statement::create()
            ->query('DROP TABLE IF EXISTS ' . join(',', $tableList))
            ->execute()
        ;
        return $this
            ->createTableModules($stmt)
            ->createTableModulesActions($stmt)
        ;
    }


    /**
     *
     */
    protected function createTableModules($stmt)
    {
        $titleCols = $this->getTitleColumns();
        $q = <<<sql
CREATE TABLE yz_modules_tmp (
    id              smallint(5) unsigned NOT NULL PRIMARY KEY
                    COMMENT 'use only for DB relations',
    name            varchar(255) character set latin1 NOT NULL UNIQUE,
    isWeb           tinyint(4) NOT NULL,
    {$titleCols}
    KEY(isWeb)
)
sql;
        $stmt
            ->query($q)
            ->execute()
        ;
        return $this;
    }

    /**
     *
     */
    protected function createTableModulesActions($stmt)
    {
        $titleCols = $this->getTitleColumns();
        $q = <<<sql
CREATE TABLE yz_modules_actions_tmp (
    id              smallint(5) unsigned NOT NULL PRIMARY KEY
                    COMMENT 'use only for DB relations',
    name            varchar(255) character set latin1 NOT NULL,
    moduleId        smallint(5) unsigned NOT NULL,
    inMenu          tinyint(4) unsigned NOT NULL DEFAULT 0,
    accessType      tinyint(4) unsigned NOT NULL DEFAULT 0,
    className       varchar(255) character set latin1 NOT NULL DEFAULT '',
    arguments       varchar(255) character set latin1 NOT NULL DEFAULT '',
    {$titleCols}
    UNIQUE(moduleId, name)
)
sql;
        $stmt
            ->query($q)
            ->execute()
        ;
        return $this;
    }

    protected function getTitleColumns()
    {
        static $titleCols;
        if (null === $titleCols) {
            $titleCols = "";
            $langs = \I18N::getInstalledLanguages();
            foreach ($langs as $langCode => $langTitle) {
                $this->titleCols[] = "title_{$langCode}";
                $titleCols .= "    title_{$langCode} varchar(255) NOT NULL,\n";
            }
        }
        return $titleCols;
    }


    /** build the list of modules used by application, except abstract ones
     *  @return Yz_Hash {moduleName: ReflectionClass}
     */
    protected function buildModulesList()
    {
        $list =& $this->modulesList;
        if (null === $list) {
            $allNS = \Yz::getInstalledNamespaces();
            $list = array();
            foreach ($allNS as $ns) {
                $nlist = glob(\YZ_LIB . $ns . '/modules/*', GLOB_ONLYDIR);
                if ($nlist) {
                    foreach ($nlist as $modDir) {
                        $modName = basename($modDir);
                        if (! isset($list[$modName])) {
                            $file = $modDir . '/Module.php';
                            if (is_file($file)) {
                                include_once($file);
                            }
                            $class = $ns . '\\modules\\' . $modName . '\\Module';
                            $refl = new \ReflectionClass($class);
                            if ($refl->isAbstract()) {
                                continue;
                            }
                            if (! $refl->implementsInterface('Yz_Module_Interface')) {
                                \Log::log("Bad module class {$class}");
                                continue;
                            }
                            $list[$modName] = $refl;
                        }
                    }
                }
            }
        }
        return $list;
    }

    public function buildModulesAttrs()
    {
        $list = $this->buildModulesList();
        $intlNames = array();
        $langs = array_keys(\I18N::getInstalledLanguages());
        foreach ($list as $modName => $refl) {
            $class = $refl->getName();
            $mod = $class::getInstance();
            try {
                $i18n = $mod->getI18N();
            } catch (\Yz_Exception $ex) {
                continue;
            }
            $attrs = array(
                'name' => $modName,
                'isWeb' => (int)$refl->implementsInterface('Yz_Module_WebModule_Interface'),
            );
            // try to preserve old id
            if (isset($this->oldModulesList[$modName])) {
                $modItem = $this->oldModulesList[$modName];
                $attrs['id'] = $modItem->id;
            } else {
                $attrs['id'] = $this->generateUniqueId($this->oldModulesList);
            }
            foreach ($langs as $langCode) {
                $title = $i18n->translateToLang("#Module", $langCode);
                if ('#Module' === $title) {
                    $title = "";
                }
                $attrs["title_" . $langCode] = $title;
            }
            $intlNames[$modName] = $attrs;
        }
        return $intlNames;
    }

    public function buildCtlList()
    {
        $list = $this->buildModulesList();
        $actNames = array();
        foreach ($list as $modName => $refl) {
            $class = $refl->getName();
            $mod = $class::getInstance();
            try {
                $i18n = $mod->getI18N();
            } catch (\Yz_Exception $ex) {
                continue;
            }
            $ctlList = $this->buildCtlListForModule($mod)
                and $actNames[$modName] = $ctlList;
        }
        return $actNames;
    }



    /** get the list of all controllers defined in the module
     *  the method relies on the assumption that controller classes are not redefined in
     *  the namespaced constants
     *  @param Yz_Module_Interface $module
     *  @return array
     */
    public function buildCtlListForModule(\Yz_Module_Interface $module)
    {
        debug();
        $list = array();
        $moduleId = $this->moduleAttrs[$module->getName()]['id'];
        $toScan = array($module) + class_parents($module);
        foreach ($toScan as $modClass) {
            $ns = $modClass::_ns_();
            if ("" === $ns) {
                break;
            }
            debug("Scanning module {$modClass}");
            $path = strtr($ns, '\\', '/');
            $glob = YZ_LIB . '/' . $path . '/Controller/*.php';
            debug("Scanning path {$glob}");
            $files = glob($glob);
            if ($files) {
                foreach ($files as $file) {
                    $ctlName = basename($file, '.php');
                    debug("Scanning file {$file}, controller {$ctlName}");
                    if (! array_key_exists($ctlName, $list)) {
                        debug("Adding controller {$ctlName}");
                        include_once($file);
                        $class = $ns . '\\Controller_' . $ctlName;
                        $refl = new \ReflectionClass($class);
                        if (! $refl->isAbstract()) {
                            $ctlInfo = array(
                                'moduleId' => $moduleId,
                                'inMenu' => (int)$refl->getConstant('IN_MENU'),
                                'name' => $ctlName,
                                'className' => $class,
                                'accessType' => $refl->getConstant('ACCESS'),
                                'arguments' => '',
                            );
                            $props = $refl->getDefaultProperties();
                            if (isset($props['arguments'])) {
                                $prop = $props['arguments'];
                                is_array($prop)
                                    and $prop
                                    and $ctlInfo['arguments'] = join(',', $prop);
                            }
                            $i18n = $module->getI18N();
                            foreach (array_keys($i18n->getInstalledLanguages()) as $lang) {
                                $ctlInfo['title_' . $lang] = $i18n->translateToLang(':' . $ctlName, $lang);
                            }
                            // try to preserve old id
                            if (isset($this->oldCtlList[$ctlName])) {
                                $ctlItem = $this->oldCtlList[$ctlName];
                                $ctlInfo['id'] = $ctlItem->id;
                            } else {
                                $ctlInfo['id'] = $this->generateUniqueId($this->oldCtlList);
                            }
                            $list[$ctlName] = $ctlInfo;
                        }
                    }
                }
            }
        }
        return $list;
    }


    /** create a list of items and assign values to them
     *  @param hash
     *  @param item template item
     *  @return hash{item}
     */
    protected function assignValues($values, $item)
    {
        $list = parent::assignValues($values, $item);
        foreach ($values as $value) {
            $list[$value->name] = $list[$value->id];
        }
        return $list;
    }


    protected function generateUniqueId( &$list )
    {
        do {
            $id = rand(1, 65535);
        }
        while (isset($list[$id]));
        $list[$id] = 1;
        return $id;
    }

    protected function storeModuleAttrs( $attrs )
    {
        $fields = array_merge(
            array('id', 'name', 'isWeb'),
            $this->titleCols
        );
        debug("Fields: " . join(',', $fields));
        $stmt = \Yz_SQL_Statement_Insert::create()
            ->insert('yz_modules_tmp')
            ->fields($fields)
            ->values(array_map(function($val){
                return ':' . $val;
            }, $fields));

        foreach ($attrs as $entry) {
            $stmt
                ->bindArray($entry)
                ->execute();
        }
        return $this;
    }

    protected function storeCtlAttrs( $attrs )
    {
        $fields = array_merge(
            array(
                'id',
                'moduleId',
                'inMenu',
                'name',
                'className',
                'accessType',
                'arguments',
            ),
            $this->titleCols
        );
        debug("Fields: " . join(',', $fields));
        $stmt = \Yz_SQL_Statement_Insert::create()
            ->insert('yz_modules_actions_tmp')
            ->fields($fields)
            ->values(array_map(function($val){
                return ':' . $val;
            }, $fields));

        foreach ($attrs as $modEntry) {
            foreach ($modEntry as $entry) {
                $stmt
                    ->bindArray($entry)
                    ->execute();
            }
        }
        return $this;
    }


}
